package com.blah.gwtgames.client.sudoku;

import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class Sudoku extends Composite
{
	private Cell cells[][];
	private Panel panel = new VerticalPanel();
	int base;
	boolean rowHighlights[];
	boolean columnHighlights[];
	boolean blockHighlights[][];
	
	public Sudoku(int base)
	{
		this(base, null);
	}

	public Sudoku(int base, CellValue[][] values)
	{
		this.base = base;
		int dimension = getDimension();
		cells = new Cell[dimension][dimension];
		
		rowHighlights = new boolean[dimension];
		columnHighlights = new boolean[dimension];
		blockHighlights = new boolean[base][base];
	
		for (int gridY = 0; gridY < base; ++gridY)
		{
			Panel gridRow = new HorizontalPanel();
			for (int gridX = 0; gridX < base; ++gridX)
			{
				Panel block = new VerticalPanel();
				block.addStyleName("sudoku-Block");
				for (int blockY = 0; blockY < base; ++blockY)
				{
					Panel blockRow = new HorizontalPanel();
					for (int blockX = 0; blockX < base; ++blockX)
					{
						final int x = gridX * base + blockX;
						final int y = gridY * base + blockY;
						Cell cell;
						
						if (values != null)
							cell = new Cell(values[x][y]);
						else
							cell = new Cell();
						
						blockRow.add(cell);
						cells[x][y] = cell;
						
						cell.addClickListener(new ClickListener()
						{
							final Sudoku sudoku = Sudoku.this;
							
							public void onClick(Widget sender)
							{
								sudoku.onCellClicked(x, y);
							}
							
						});
						
						cell.addKeyboardListener(new KeyboardListener()
						{
							final Sudoku sudoku = Sudoku.this;
							
							public void onKeyDown(Widget sender, char keyCode, int modifiers)
							{
								sudoku.onCellKeyPressed(x, y, keyCode);
							}

							public void onKeyPress(Widget sender, char keyCode, int modifiers)
							{}

							public void onKeyUp(Widget sender, char keyCode, int modifiers)
							{}
							
						});
					}
					block.add(blockRow);
				}
				gridRow.add(block);
			}
			panel.add(gridRow);
		}
		
		initWidget(panel);	
	}
	
	protected void onCellKeyPressed(int x, int y, char keyCode)
	{
		Cell cell = cells[x][y];
		
		if (!cell.isLocked())
		{
			if (keyCode == '+')
				setValue(x, y, (cell.getValue() + 1) % 9);
			
			if (keyCode == '-')
				cell.setValue((cell.getValue() - 1) % 9);
			
			if (keyCode >= '0' && keyCode <= '9')
				setValue(x, y, keyCode - '0');
			
			// Actually the number pad, gwt just encodes it this way
			if (keyCode >= '`' && keyCode <= 'i')
				setValue(x, y, keyCode - '`');
			
			if (keyCode == KeyboardListener.KEY_BACKSPACE ||
				keyCode == KeyboardListener.KEY_DELETE)
				setValue(x, y, 0);
		}
				
		if (keyCode == 'W' ||
			keyCode == KeyboardListener.KEY_UP)
			if (--y >= 0)
				cells[x][y].setFocus(true);
		
		if (keyCode == 'S' ||
			keyCode == KeyboardListener.KEY_DOWN)
			if (++y < getDimension())
				cells[x][y].setFocus(true);
		
		if (keyCode == 'A' ||
			keyCode == KeyboardListener.KEY_LEFT)
			if (--x >= 0)
				cells[x][y].setFocus(true);
		
		if (keyCode == 'D' ||
			keyCode == KeyboardListener.KEY_RIGHT)
			if (++x < getDimension())
				cells[x][y].setFocus(true);
	}
	
	public void setValue(int x, int y, int value)
	{
		Cell cell = cells[x][y];
		int oldValue = cell.getValue();
		
		if(oldValue != value)
		{
			cell.setValue(value);
			cell.clearConflicts();
			setRowValue(x, y, oldValue, value);
			setColumnValue(x, y, oldValue, value);
			setBlockValue(x, y, oldValue, value);
		}
	}
	
	private void setRowValue(int x, int y, int oldValue, int newValue)
	{
		boolean complete = true;
		boolean usedValues[] = new boolean[getDimension()];
		
		for (int i = 0; i < getDimension(); ++i)
		{
			int cellValue = cells[i][y].getValue();
			if (cellValue != 0)
			{
				if (cellValue == oldValue)
					cells[i][y].setConflicted(false);
				
				if (cellValue == newValue && i != x)
				{
					cells[i][y].setConflicted(true);
					cells[x][y].setConflicted(true);
				}
				
				if (usedValues[cellValue - 1])
					complete = false;
				
				usedValues[cellValue - 1] = true;	
			}
			else
			{
				complete = false;
			}
		}
		
		if (complete != rowHighlights[y])
		{
			rowHighlights[y] = complete;
			for (int i = 0; i < getDimension(); ++i)
			{
				cells[i][y].setHighlighted(complete);
			}
		}
	}
	
	public int getValue(int x, int y)
	{
		return cells[x][y].getValue();
	}
	
	public boolean isLocked(int x, int y)
	{
		return cells[x][y].isLocked();
	}
	
	private void setColumnValue(int x, int y, int oldValue, int newValue)
	{
		int dimension = getDimension();
		boolean complete = true;
		boolean usedValues[] = new boolean[dimension];
		
		for (int i = 0; i < dimension; ++i)
		{
			int cellValue = cells[x][i].getValue();
			if (cellValue != 0)
			{
				if (cellValue == oldValue)
					cells[x][i].setConflicted(false);
				
				if (cellValue == newValue && y != i)
				{
					cells[x][i].setConflicted(true);
					cells[x][y].setConflicted(true);
				}
				
				if (usedValues[cellValue - 1])
					complete = false;
				
				usedValues[cellValue - 1] = true;
			}
			else
			{
				complete = false;
			}
		}
		
		if (complete != columnHighlights[x])
		{
			columnHighlights[x] = complete;
			for (int i = 0; i < dimension; ++i)
			{
				cells[x][i].setHighlighted(complete);
			}
		}
	}
	
	private void setBlockValue(int x, int y, int oldValue, int newValue)
	{
		boolean complete = true;
		boolean usedValues[] = new boolean[getDimension()];
		int blockX = x - (x % base);
		int blockY = y - (y % base);
		
		for (int i = blockX; i < blockX + base; ++i)
		{
			for (int j = blockY; j < blockY + base; ++j)
			{
				int cellValue = cells[i][j].getValue();
				if (cellValue != 0)
				{
					if (cellValue == oldValue)
						cells[i][j].setConflicted(false);
					
					if (cellValue == newValue && (i != x || j != y))
					{
						cells[i][j].setConflicted(true);
						cells[x][y].setConflicted(true);
					}
					
					if (usedValues[cellValue - 1])
						complete = false;
					
					usedValues[cellValue - 1] = true;
				}
				else
				{
					complete = false;
				}
			}
		}
		
		if (complete != blockHighlights[x / base][y / base])
		{
			blockHighlights[x / base][y / base] = complete;
			for (int i = blockX; i < blockX + base; ++i)
			{
				for (int j = blockY; j < blockY + base; ++j)
				{
					cells[i][j].setHighlighted(complete);
				}
			}
		}
	}
		
	private void onCellClicked(int x, int y)
	{
		cells[x][y].setFocus(true);
	}

	public int getDimension()
	{
		return base * base;
	}

	public void lockValues()
	{
		int dimension = getDimension();
		for (int x = 0; x < dimension; ++x)
		{
			for (int y = 0; y < dimension; ++y)
			{
				if (cells[x][y].getValue() != 0)
				{
					cells[x][y].setLocked(true);
				}
			}
		}
	}
}
